﻿using System;
using Lichtspiel.Audio;
using Lichtspiel.Display;
using Lichtspiel.Input;
using OpenTK;
using OpenTK.Graphics;
using Vector2 = Lichtspiel.Math.Vector2;

namespace Lichtspiel
{
    // TODO dispose
    public abstract class Game : IDisposable
    {
        private bool disposed = false;
        internal GameWindow window;
        private GameTime gameTime;

        public bool IsShowingRenderFrequency {
            get; set;
        }

        public Keyboard Keyboard
        {
            get;
            private set;
        }

        public Mouse Mouse {
            get;
            private set;
        }

        /// <summary>
        /// Gets or Sets the windows X position.
        /// </summary>
        public int X {
            get { return window.X; }
            set { window.X = value; }
        }

        /// <summary>
        /// Gets or Sets the windows Y position.
        /// </summary>
        public int Y {
            get { return window.Y; }
            set { window.Y = value; }
        }

        /// <summary>
        /// Gets or Sets the windows width.
        /// </summary>
        public int Width {
            get { return window.Width; }
            set { window.Width = value; }
        }

        /// <summary>
        /// Gets or Sets the windows height.
        /// </summary>
        public int Height {
            get { return window.Height; }
            set { window.Height = value; }
        }

        /// <summary>
        /// Sets the windows title.
        /// // TODO getter geht nich
        /// </summary>
        public string Title {
            set { window.Title = value; }
        }

        /// <summary>
        /// Update FPS
        /// </summary>
        public double UpdateFrequency {
            get { return window.UpdateFrequency; }
        }

        /// <summary>
        /// Time spent in the update method
        /// </summary>
        public TimeSpan UpdateTime {
            get { return TimeSpan.FromSeconds(window.UpdateTime); }
        }

        /// <summary>
        /// Render FPS
        /// </summary>
        public double RenderFrequency {
            get { return window.RenderFrequency; }
        }

        /// <summary>
        /// Time spent in the render method
        /// </summary>
        public TimeSpan RenderTime {
            get { return TimeSpan.FromSeconds(window.RenderTime); }
        }

        public GraphicsCore Graphics { get; private set; }
        public AudioCore Audio { get; private set; }

        public Game(int width, int height)
            : this(width, height, "Lichtspiel Game", false) { }

        public Game(int width, int height, string title, bool fullscreen) {
            window = new GameWindow(width, height, GraphicsMode.Default, title,
                                          fullscreen ? GameWindowFlags.Fullscreen : GameWindowFlags.Default);

            window.Load += OnLoad;
            window.Unload += OnUnload;
            window.Resize += OnResize;
            window.UpdateFrame += OnUpdateFrame;
            window.RenderFrame += OnRenderFrame;

            this.gameTime = new GameTime(TimeSpan.Zero, TimeSpan.Zero);

            this.Graphics = new GraphicsCore(this);
            this.Audio = new AudioCore(this);
            this.Keyboard = new Keyboard(this);
            this.Mouse = new Mouse(this);
            ImageFont.LoadDefaultFonts();
        }

        public void Run() {
            window.Run();
        }

        public void Run(double updateRate) {
            window.Run(updateRate);
        }

        public void Run(double updatesPerSecond, double framesPerSecond) {
            window.Run(updatesPerSecond, framesPerSecond);
        }

        public void Quit() {
            window.Exit();
        }

        /// <summary>
        /// Will be called before displaying the first time.
        /// </summary>
        protected abstract void Load();

        /// <summary>
        /// Will be called before destroying.
        /// </summary>
        protected abstract void Unload();

        protected abstract void Update(GameTime time);

        protected abstract void Render();

        private void OnLoad(Object o, EventArgs e) {
            Graphics.Initialize();
            Load();
        }

        private void OnUnload(Object o, EventArgs e) {
            Unload();
            this.Mouse.ShowCursor(); // because otherwise the cursor will be invi in external windows
        }

        private void OnUpdateFrame(Object o, FrameEventArgs e) {
            gameTime.Elapsed = TimeSpan.FromSeconds(e.Time);
            gameTime.Total += gameTime.Elapsed;

            this.Keyboard.Update(gameTime);
            this.Mouse.Update(gameTime);

            Update(gameTime);
        }

        private void OnRenderFrame(Object o, FrameEventArgs e) {
            Graphics.Clear();
            Render();
            DrawHud();
            window.SwapBuffers();
        }

        private void DrawHud() {
            if (IsShowingRenderFrequency) {
                ImageFont.Default.DrawText("RFPS: " + (int) this.RenderFrequency, new Vector2(10, 10), Color.Black);
            }
        }

        private void OnResize(Object o, EventArgs e) {
            Graphics.Resize(Width, Height);
        }

        #region IDisposable Members

        public void Dispose() {
            Dispose(true);
        }

        private void Dispose(bool disposing) {
            // If you need thread safety, use a lock around these 
            // operations, as well as in your methods that use the resource.
            if (!disposed) {
                if (disposing) {
                    if (window != null) {
                        window.Dispose();
                    }
                }

                // Indicate that the instance has been disposed.
                window = null;
                disposed = true;
            }
        }

        #endregion


    }
}